//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <ddk.h>

//Copy from bsp
// flash baseaddr on normal mode
#define HwFlashBase         0x00000000
#define HwFlashSize         0x02000000  /*32M*/

#define HwFlashEraseUnitSize   0x00020000// 128k erase unit

//==========================================================================
//flash operator functions
//==========================================================================
EXTERN_C int NorFlashEraseSector (unsigned long FlashBase, long Offset);
EXTERN_C int NorFlashWriteBuffer (unsigned long FlashBase, long Offset, unsigned char *Buffer, long BufferSize);
EXTERN_C int NorFlashReadBuffer  (unsigned long FlashBase, long Offset, unsigned char *Buffer, long BufferSize);

//
// Norflash
//
class Norflash : public CDeviceDriver {
public:
    CARAPI Read(
        /* [in] */ Int64 u64Offset,
        /* [in] */ Int32 bytesToRead,
        /* [out] */ MemoryBuf * pBuffer,
        /* [out] */ IEvent * * ppCompletionEvent);

    CARAPI Write(
        /* [in] */ Int64 u64Offset,
        /* [in] */ const MemoryBuf & buffer,
        /* [out] */ Int32 * pBytesWritten,
        /* [out] */ IEvent * * ppCompletionEvent);

    CARAPI Control(
        /* [in] */ Handle32 nControlCode,
        /* [in] */ const MemoryBuf & inBuffer,
        /* [out] */ MemoryBuf * pOutBuffer,
        /* [out] */ IEvent * * ppCompletionEvent);

    virtual void Dispose();

public:
    Norflash();
    ECode Initialize(Address physbase, UInt32 size);

private:
    Address     m_physbase;
    Address     m_virtbase;
    UInt32      m_size;

    DzMutex   	m_lock;
};

Norflash::Norflash()
{
    m_virtbase = 0;
}

ECode Norflash::Initialize(Address physbase, UInt32 size)
{
    assert(m_virtbase == 0);

    m_physbase = physbase;
    m_size = size;

    m_virtbase = (Address)DzKIoRemap(m_physbase, m_size);
    if (0 == m_virtbase) {
        kprintf("KIoRemap failed\n");
        return E_FAIL;
    }

    assert(0 != m_virtbase);
    kprintf("Mapped 0x%x to 0x%x\n", m_physbase, m_virtbase);

    return NOERROR;
}

ECode Norflash::Read(
    /* [in] */ Int64 u64Offset,
    /* [in] */ Int32 bytesToRead,
    /* [out] */ MemoryBuf * pBuffer,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    if (pBuffer == NULL || !pBuffer->GetCapacity()
        || (int)bytesToRead > pBuffer->GetCapacity()
        || 0 == bytesToRead
        || m_size < u64Offset + bytesToRead
        || ((u64Offset + bytesToRead) & 0x1)
        ) {
        return E_INVALID_ARGUMENT;
    }

    WaitResult wr;
    m_lock.Lock(&wr);

    NorFlashReadBuffer(
        (unsigned long)m_virtbase,
        (long)u64Offset,
        (unsigned char *)pBuffer->GetPayload(),
        bytesToRead);

    pBuffer->SetUsed(bytesToRead);

    m_lock.Unlock();

    return NOERROR;
}

ECode Norflash::Write(
    /* [in] */ Int64 u64Offset,
    /* [in] */ const MemoryBuf & buffer,
    /* [out] */ Int32 * pBytesWritten,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    if (buffer.IsNullOrEmpty()
        || m_size < u64Offset + buffer.GetUsed()
        || (HwFlashEraseUnitSize < (u64Offset&0x1ffff) + buffer.GetUsed())
        || NULL == pBytesWritten
        || ((u64Offset + buffer.GetUsed()) & 0x1)
        ) {
         return E_INVALID_ARGUMENT;
    }

    WaitResult wr;
    m_lock.Lock(&wr);

    NorFlashWriteBuffer(
        (unsigned long)m_virtbase,
        (long)u64Offset,
        (unsigned char *)buffer.GetPayload(),
        (long)buffer.GetUsed());

//    if (0 != memcmp((char *)buffer,
//        (char *)m_virtbase + (long)u64Offset, (long)buffer.GetUsed())) {
//        kprintf("*ERROR* Write flash error!\n");
//    }

    *pBytesWritten = (UInt32)buffer.GetUsed();

    m_lock.Unlock();

    return NOERROR;
}

typedef struct FlashInfo {
    UInt32 Size;
    UInt32 EraseUnitSize;
} FlashInfo;

ECode Norflash::Control(
    /* [in] */ Handle32 nControlCode,
    /* [in] */ const MemoryBuf & inBuffer,
    /* [out] */ MemoryBuf * pOutBuffer,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    FlashInfo *pInfo;
    UInt32 offset;
    WaitResult wr;

    switch (nControlCode) {
    case 1:
        if (sizeof(UInt32) != (size_t)inBuffer.GetUsed()) {
            return E_INVALID_ARGUMENT;
        }

        offset = *(UInt32 *)inBuffer.GetPayload();
        offset = RoundDown2(offset, HwFlashEraseUnitSize);
        if (offset >= m_size) return E_INVALID_ARGUMENT;

//        kprintf("Erasing 0x%x, 0x%x ...\n",
//                (unsigned long)m_virtbase, offset);

        m_lock.Lock(&wr);
        NorFlashEraseSector((unsigned long)m_virtbase, offset);
        m_lock.Unlock();

        break;

    case 2:
        if (pOutBuffer == NULL ||
            sizeof(FlashInfo) > (size_t)pOutBuffer->GetCapacity()) {
            return E_INVALID_ARGUMENT;
        }

        pInfo = (FlashInfo *)pOutBuffer->GetPayload();
        pInfo->Size = m_size;
        pInfo->EraseUnitSize = HwFlashEraseUnitSize;
        pOutBuffer->SetUsed(sizeof(FlashInfo));

        break;

    default:
        return E_INVALID_OPTIONS;
    }

    return NOERROR;
}

void Norflash::Dispose()
{
    delete this;
}

EXTERN IDeviceDriver * CDECL CreateStorage(uint_t uDeviceNo, void *pvParameter)
{

    ECode hr;
    Norflash *pStorage = new Norflash();
    if (NULL == pStorage) return NULL;

    hr = pStorage->Initialize(HwFlashBase, HwFlashSize);
    assert(HwFlashEraseUnitSize == 128*1024/**/);
    assert(HwFlashEraseUnitSize < HwFlashSize);
    return pStorage;
}
